package com.qdxwx.payment.wxpay;


import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.qdxwx.common.utils.DateUtils;
import com.qdxwx.common.utils.TKServlet;
import com.qdxwx.common.utils.TKRequest;
import com.qdxwx.common.utils.TKString;
import com.qdxwx.models.order.Order;
import com.qdxwx.payment.wxpay.httpclient.util.AesUtil;
import com.qdxwx.payment.wxpay.httpclient.util.PemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Map;

/**
 * @author ZhiYong
 * @updateTime 2021/12/21
 * @description WxPay(v3版本)
 */
public class WxPay {

    private static final Logger logger = LoggerFactory.getLogger(WxPay.class);

    private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/";

    private static final String NOTIFY_URL = "https://www.dwvan.com/order/wxpay/notify";

    /**
     * 微信公众号支付请求(jsapi支付) 发送预支付订单返回参数
     */
    public static String JSAPI(Order order, String openId, Map<String, Object> payConfig) {

        JSONObject params = createJSONParams(order, payConfig);
        //用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
        JSONObject payer = new JSONObject();
        payer.put("openid", openId);
        params.put("payer", payer);

        WxPayRequest payRequest = new WxPayRequest(payConfig);
        // 预支付订单创建结果
        String resStr = payRequest.post(PAY_BASE_URL + "jsapi", params.toString());

        JSONObject resJson = JSON.parseObject(resStr);

        String prepayId = resJson.getString("prepay_id");
        if (prepayId == null) {
            logger.error("JSAPI:" + resJson);
            throw new RuntimeException(resJson.getString("message"));
        }
        return prepayId;
    }

    /**
     * 获取微信支付的二维码地址(v3版本)
     */
    public static String qrCode(Order order, Map<String, Object> payConfig) {

        JSONObject params = createJSONParams(order, payConfig);
        WxPayRequest payRequest = new WxPayRequest(payConfig);
        String resStr = payRequest.post(PAY_BASE_URL + "native", params.toString());

        JSONObject resJson = JSON.parseObject(resStr);

        String codeUrl = resJson.getString("code_url");
        if (codeUrl == null) {
            logger.error("qrCode:" + resJson);
            throw new RuntimeException(resJson.getString("message"));
        }
        return codeUrl;
    }

    /**
     * 获取微信支付的H5支付地址(v3版本)
     */
    public static String h5(Order order, Map<String, Object> payConfig) {
        JSONObject params = createJSONParams(order, payConfig);
        //h5场景
        JSONObject h5Info = new JSONObject();
        //场景类型
        //示例值：iOS, Android, Wap
        h5Info.put("type", "Wap");
        h5Info.put("app_name", payConfig.get("appName"));//应用名称
        h5Info.put("app_url", payConfig.get("appUrl"));//应用网址
        //支付场景描述 h5 必选
        JSONObject sceneInfo = new JSONObject();
        sceneInfo.put("payer_client_ip", TKRequest.getIpAddr(TKServlet.getRequest()));
        sceneInfo.put("h5_info", h5Info);
        params.put("scene_info", sceneInfo);
        WxPayRequest payRequest = new WxPayRequest(payConfig);
        String resStr = payRequest.post(PAY_BASE_URL + "h5", params.toString());

        JSONObject resJson = JSON.parseObject(resStr);

        String h5Url = resJson.getString("h5_url");
        if (h5Url == null) {
            logger.error("h5:" + resJson);
            throw new RuntimeException(resJson.getString("message"));
        }
        return h5Url;
    }

    public static JSONObject createJSONParams(Order order, Map<String, Object> payConfig) {
        //价钱(转为分)
        int total_fee = order.getCashAmount();
        //封装参数
        JSONObject params = new JSONObject();
        params.put("mchid", payConfig.get("mchId"));
        /*由微信生成的应用ID，全局唯一。请求基础下单接口时请注意APPID的应用属性，例如公众号场景下，需使用应用属性为公众号的APPID*/
        params.put("appid", payConfig.get("appId"));
        //商品描述
        params.put("description", order.getDescription());
        //回调地址
        String notifyUrl = (String) payConfig.get("notifyUrl");
        if (notifyUrl == null || notifyUrl.length() < 5) {
            params.put("notify_url", NOTIFY_URL);
        } else {
            params.put("notify_url", notifyUrl);
        }
        /*商户系统内部订单号，只能是数字、大小写字母_-*且在同一个商户号下唯一 示例值：1217752501201407033233368018*/
        params.put("out_trade_no", order.getOrderNo());
        //过期时间
        params.put("time_expire", DateUtils.toString(LocalDateTime.now().plusMinutes(35), "yyyy-MM-dd'T'HH:mm:ss'+08:00'"));
        //订单总金额，单位为分
        JSONObject amount = new JSONObject();
        amount.put("total", total_fee);
        amount.put("currency", "CNY");//CNY人民币;
        params.put("amount", amount);
        //额外的参数，原样返回
        params.put("attach", order.getOrderNo());
        System.out.println("wxParams:" + params);
        return params;
    }

    /**
     * 获取支付通知资源
     *
     * @param request
     * @return
     */
    public static JSONObject getNotifyResource(HttpServletRequest request) {
        //读取Body
        String retBody = TKRequest.getRequestBody(request);
        if (TKString.isEmpty(retBody)) {
            return null;
        }

        JSONObject retMap = JSONObject.parseObject(retBody);

        if (retMap == null || !"TRANSACTION.SUCCESS".equals(retMap.getString("event_type"))) {
            return null;
        }
        //"original_type": "transaction",
        //"algorithm": "AEAD_AES_256_GCM",
        //"ciphertext": "",
        //"associated_data": "",
        //"nonce": ""
        return retMap.getJSONObject("resource");
    }

    /**
     * 获取通知资源解码
     *
     * @param resourceMap
     * @param apiKey
     * @return
     */
    public static JSONObject getResourceDecrypt(JSONObject resourceMap, String apiKey) {
        try {
            String originalType = resourceMap.getString("original_type");
            String algorithm = resourceMap.getString("algorithm");
            String ciphertext = resourceMap.getString("ciphertext");
            String associatedData = resourceMap.getString("associated_data");
            String nonce = resourceMap.getString("nonce");

            AesUtil aesUtil = new AesUtil(apiKey.getBytes(StandardCharsets.UTF_8));
            //ciphertext
            String decryptText = aesUtil.decryptToString(associatedData.getBytes(), nonce.getBytes(), ciphertext);
            //cipherMap
            return JSONObject.parseObject(decryptText);
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            return null;
        }
    }

    /**
     * 支付回调返回给支付接口的应答
     *
     * @param message 返回消息(成功为：SUCCESS，其它为失败消息)
     * @return Map<String, String>
     */
    public static JSONObject getNotifyReply(String message) {
        JSONObject reply = new JSONObject();
        reply.put("code", "SUCCESS".equals(message) ? "SUCCESS" : "ERROR");
        reply.put("message", message);
        return reply;
    }

    public static String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }

    public static String getSign(Map<String, String> params, String privateKey) {
        String message = String.format("%s\n%s\n%s\n%s\n"
                , params.get("appId")
                , params.get("timeStamp")
                , params.get("nonceStr")
                , params.get("package")
        );
        return getSign(message, privateKey);
    }

    public static String getSign(String message, String privateKey) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(PemUtil.loadPrivateKey(privateKey));
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
        } catch (SignatureException e) {
            throw new RuntimeException("签名计算失败", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("无效的私钥", e);
        }
    }
}
